import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { apiSafe } from "@/lib/api"; import { PartyBadge } from "@/components/party"; import { Badge, Card, KV, SectionHeader, SourceBadge } from "@/components/ui"; import { dateTimeFr } from "@/lib/format"; import { partyVar, partyName } from "@/lib/parties"; export const revalidate = 300; interface Doc { id: number; party: string; title: string; url: string; type: string; status: string; discoveredAt: string; lastCheckedAt: string | null; versions: { id: number; sha256: string; fetchedAt: string; bytes: number; pages: number | null; contentType: string | null; analyzedAt: string | null; diff: { ratio: number; added: string[]; removed: string[]; added_count: number; removed_count: number } | null; current: boolean; archiveUrl: string }[]; proposals: { id: number; topic: string; topicLabel: string; subtopic: string | null; proposal: string; position: string | null; direction: string | null; magnitude: string | null; page: number | null; quote: string; confidence: number; status: string }[] } export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise { const { id } = await params; const d = await apiSafe(`/api/documents/${id}`); return { title: d ? `${d.title} — ${partyName(d.party)}` : "Document" }; } export default async function Page({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const d = await apiSafe(`/api/documents/${id}`); if (!d) notFound(); const cur = d.versions.find((v) => v.current) ?? d.versions[d.versions.length - 1]; const byTopic: Record = {}; d.proposals.filter((p) => p.status === "accepted").forEach((p) => { (byTopic[p.topicLabel] ??= []).push(p); }); return (
← Bibliothèque
{d.type.replace("_", " ")}{cur?.contentType?.includes("pdf") && PDF · {cur.pages} pages}

{d.title}

{Object.keys(byTopic).length === 0 ?
Aucune proposition acceptée n'a été extraite de ce document (page de navigation, contenu non politique, ou analyse en attente).
: Object.entries(byTopic).map(([label, ps]) => (
{ps.map((p) => (
{p.proposal}
{p.position &&
{p.position}
}{p.magnitude &&
{p.magnitude}
}
« {p.quote} »
{p.page ?
Page {p.page}
: null}
Confiance {Math.round(p.confidence * 100)} %
{p.direction && p.direction !== "neutre" &&
{p.direction.replace(/_/g, " ")}
}{p.subtopic &&
{p.subtopic}
}
))}
))}
Fiche
p.status === "accepted").length)], ["En révision", String(d.proposals.filter((p) => p.status === "review").length)]]} />
Versions
    {d.versions.slice().reverse().map((v) => (
  • {dateTimeFr(v.fetchedAt)}{v.current && courante}
    {v.sha256}
    {(v.bytes / 1024).toFixed(0)} Ko{v.pages ? ` · ${v.pages} p.` : ""} · archive QC26
    {v.diff &&
    Δ vs version précédente : similarité {Math.round(v.diff.ratio * 100)} %, +{v.diff.added_count} / −{v.diff.removed_count} lignes
    }
  • ))}
{cur?.diff && (cur.diff.added.length > 0 || cur.diff.removed.length > 0) && (
Document diff
{cur.diff.added.slice(0, 30).map((l, i) =>
+ {l}
)}{cur.diff.removed.slice(0, 30).map((l, i) =>
− {l}
)}
)}
); }